﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using DevExpress.XtraCharts;
using Genetic;
using System.Diagnostics;

namespace TSP {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();


            // Scale to 100 by 100
            ((XYDiagram)chartControl1.Diagram).AxisX.Range.MinValue = 0;
            ((XYDiagram)chartControl1.Diagram).AxisX.Range.MaxValue = 99;
            ((XYDiagram)chartControl1.Diagram).AxisY.Range.MinValue = 0;
            ((XYDiagram)chartControl1.Diagram).AxisY.Range.MaxValue = 99;

            chartControl1.Series[0].Visible = true;
            chartControl1.Series[1].Visible = false;

            chartControl1.Series[0].Label.Visible = false;
            chartControl1.Series[1].Label.Visible = false;

            chartControl1.MouseDown += chartControl1_MouseDown;
            chartControl1.MouseMove += chartControl1_MouseMove;
            chartControl1.MouseUp += chartControl1_MouseUp;
        }

        #region Drag and Drop
        SeriesPoint _dragPoint = null;
        char _annotation = 'A';

        private void chartControl1_MouseDown(object sender, MouseEventArgs e) {
            _dragPoint = null;

            var ht = chartControl1.CalcHitInfo(e.X, e.Y);

            if (!chartControl1.Series[0].Visible) return;

            if (!ht.InDiagram) {
                return;
            }

            if (ht.InSeries) {
                _dragPoint = ht.SeriesPoint;
                return;
            }

            if (_annotation == 'Z') return;

            DiagramCoordinates diagramCoordinates = ((XYDiagram)chartControl1.Diagram).PointToDiagram(e.Location);

            SeriesPoint point = new SeriesPoint(
               diagramCoordinates.NumericalArgument, diagramCoordinates.NumericalValue
            );
            point.Tag = _annotation;

            point.Annotations.AddTextAnnotation(_annotation.ToString(), _annotation.ToString());

            _annotation = (char)(((byte)_annotation) + 1);
            chartControl1.Series[0].Points.Add(point);
        }

        private void chartControl1_MouseUp(object sender, MouseEventArgs e) {
            _dragPoint = null;
        }

        private void chartControl1_MouseMove(object sender, MouseEventArgs e) {
            if (_dragPoint == null) {
                return;
            }
            
            DiagramCoordinates diagramCoordinates = ((XYDiagram)chartControl1.Diagram).PointToDiagram(e.Location);

            int index = chartControl1.Series[0].Points.IndexOf(_dragPoint);

            SeriesPoint tempPoint = new SeriesPoint(
                diagramCoordinates.NumericalArgument, diagramCoordinates.NumericalValue
            );
            tempPoint.Tag = _dragPoint.Tag;

            foreach (TextAnnotation a in _dragPoint.Annotations) {
                tempPoint.Annotations.AddTextAnnotation(a.Name, a.Text);
            }

            chartControl1.Series[0].Points.Remove(_dragPoint);
            _dragPoint = tempPoint;
            chartControl1.Series[0].Points.Insert(index, _dragPoint);

        }
        #endregion

        SeriesPoint GetPointByTag(char tag) {
            foreach (SeriesPoint p in chartControl1.Series[0].Points) {
                if ((char)p.Tag == tag) {
                    return p;
                }
            }
            return null;
        }

        void Plot(Chromosome<char> chromosome) {
            chartControl1.Series[1].Points.Clear();
            foreach (char c in chromosome) {

                SeriesPoint source = GetPointByTag(c);
                Debug.Assert(source != null);

                SeriesPoint tempPoint = new SeriesPoint(
                    source.NumericalArgument, source.Values
                );
                tempPoint.Tag = source.Tag;

                foreach (TextAnnotation a in source.Annotations) {
                    tempPoint.Annotations.AddTextAnnotation(a.Name, a.Text);
                }

                chartControl1.Series[1].Points.Add(tempPoint);
            }
        }

        class TSP : Algorithm<char> {
            Form1 _form1;

            char RandomCity() {
                return _cities[Random.Next(_cities.Count)].Code;
            }
            
            class City {
                public char Code;
                public double X;
                public double Y;
            }

            List<City> _cities = new List<City>();

            public TSP(Form1 form1, int size)
                : base(size) {

                _form1 = form1;

                // Build a list of avaibale cities...
                foreach (SeriesPoint p in _form1.chartControl1.Series[0].Points) {
                    _cities.Add(new City() { 
                          Code = (char)p.Tag,
                          X = p.NumericalArgument,
                          Y = p.Values[0]
                    });
                }

                // Randomize the gene pool.
                for (int i = 0; i < Population.Length; i++) {                    
                    Population[i].SetLength(_cities.Count + 1); /* +1 for origin */
                    
                    for (int j = 0; j < Population[i].Genes.Length; j++) {
                        Population[i].Genes[j] = RandomCity();
                    }
                }
            }

            public override void Mutate(ref Chromosome<char> chromosome) {
                chromosome.Genes[Random.Next(chromosome.Genes.Length)]
                    = /* (char)Random.Next(256) */ RandomCity();
            }

            static bool IsInvalidRoute(ref Chromosome<char> chromosome) {
                List<char> hashMap = new List<char>();

                if (chromosome.Genes[0] != 'A') return true;

                for (int i = 0; i < chromosome.Genes.Length - 1; /* -1 for origin */ i++) {
                    
                    if (hashMap.Contains(chromosome.Genes[i])) {
                        return true;
                    }

                    hashMap.Add(chromosome.Genes[i]);
                }

                if (chromosome.Genes[0] 
                            != chromosome.Genes[chromosome.Genes.Length - 1]) {
                    
                    return true;
                }

                return false;
            }

            City GetCity(char code) {
                foreach (City c in _cities) {
                    if (c.Code == code) {
                        return c;
                     }
                }

                return null;
            }

            private double GetDistance(char a, char b) {
                City ca = GetCity(a);
                Debug.Assert(ca != null, "ca != null");

                City cb = GetCity(b);
                Debug.Assert(cb != null, "cb != null");

                double dx = (cb.X - ca.X);
                double dy = (cb.Y - ca.Y);

                return Math.Sqrt(dx * dx + dy * dy);
            }

            public override double GetFitness(ref Chromosome<char> chromosome) {

                if (IsInvalidRoute(ref chromosome)) {
                    
                    /* if this chromosme is invalid move it to the bottom */
                    return double.MaxValue;
                }

                double fitness = 0;

                for (int i = 0; i < chromosome.Genes.Length; i++) {
                    if (i == chromosome.Genes.Length - 1) {
                        fitness += GetDistance(chromosome.Genes[i], chromosome.Genes[0]);
                    } else {
                        fitness += GetDistance(chromosome.Genes[i], chromosome.Genes[i + 1]);
                    }

                }

                return fitness;

            }

            public override bool Epoch(int epoch) {
                if (!_form1.running) return false;
                if (!_form1.IsHandleCreated) return false;

                Chromosome<char> first = null;

                foreach (var c in Population) {
                    Chromosome<char> f = c;
                    if (!IsInvalidRoute(ref f)) {
                        first = c;
                        break;
                    }
                }

                if (first == null) return true; // continue running

                _form1.Invoke(
                  new Action(() => {
                      _form1.Plot(first);

                      if (_form1.chartControl2.Series[0].Points.Count > 100) {
                          _form1.chartControl2.Series[0].Points.Clear();
                      }
                      _form1.chartControl2.Series[0].Points.Add(
                              new SeriesPoint(epoch, first.Fitness));

                      Application.DoEvents();
                  })
                 );

                return true;
            }
        }

        private void simpleButton1_Click(object sender, EventArgs e) {
            if (chartControl1.Series[0].Points.Count <= 0) return;

            chartControl1.Series[0].Visible = false;
            chartControl1.Series[1].Visible = true;
            chartControl1.Series[1].Points.Clear();
            Application.DoEvents();

            running = true;
            simpleButton1.Enabled = false;
            simpleButton2.Enabled = true;
            simpleButton3.Enabled = false;

            TSP tsp = new TSP(this, 6000);
            tsp.Converge(300);
            simpleButton2_Click(null, null);

        }

        bool running = false;

        private void simpleButton2_Click(object sender, EventArgs e) {
            running = false;
            simpleButton1.Enabled = true;
            simpleButton2.Enabled = false;
            simpleButton3.Enabled = true;
        }

        private void simpleButton3_Click(object sender, EventArgs e) {
            chartControl1.Series[0].Visible = true;
            chartControl1.Series[1].Visible = false;
        }

    }
}
